from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model  # 注意：我的用户系统是伪系统，千万不能引入这个来加载用户类
from django.urls import reverse
from django.core import serializers

from markdownx.models import MarkdownxField
from markdownx.utils import markdownify
from slugify import slugify
from taggit.managers import TaggableManager
import os
import uuid
from collections import Counter


# 定义用户照片上传的文件路径
def user_picture_directory_path(instance, filename):  # 这两参数是django自动赋值的
    ext = filename.split('.')[-1]  # 取出用户上传的文件的文件名的后缀，即：文件类型
    filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)  # 修改文件名避免冲突
    # return the whole path to the file
    return os.path.join("profile_pics", filename)  # 生成保存路径


# 增加
def add_today_energy(user, num):
    if user.today_energy <= 0 and num <= 0:
        # 保证今日热度值不能为负值
        pass
    else:
        user.today_energy += num
        user.save()  # 千万别忘了在修改后及时进行更新


class User(models.Model):
    """自定义用户模型"""
    # user = models.OneToOneField(BlueUser, related_name='蓝鲸账户', on_delete=models.CASCADE)
    username = models.CharField(null=True, blank=True, max_length=255, verbose_name='昵称', unique=True)
    password = models.CharField(null=True, blank=True, max_length=45, verbose_name='密码')
    email = models.EmailField(null=True, blank=True, max_length=45, verbose_name='邮箱', unique=True)
    job_title = models.CharField(max_length=50, null=True, blank=True, verbose_name='职称')
    introduction = models.TextField(blank=True, null=True, verbose_name='简介', default='这个主人现在很忙，表示暂时没空修改简介。')
    picture = models.ImageField(upload_to=user_picture_directory_path, null=True, blank=True, verbose_name='头像')
    location = models.CharField(max_length=50, null=True, blank=True, verbose_name='城市')
    today_energy = models.IntegerField(null=False, blank=False, verbose_name='今日油力值', default=0)
    oneweek_energy = models.CharField(max_length=100, null=False, blank=False, verbose_name='一周油力值',
                                      default='0;0;0;0;0;0;0')
    personal_url = models.URLField(max_length=255, null=True, blank=True, verbose_name='个人链接')
    weibo = models.URLField(max_length=255, null=True, blank=True, verbose_name='微博链接')
    zhihu = models.URLField(max_length=255, null=True, blank=True, verbose_name='知乎链接')
    github = models.URLField(max_length=255, null=True, blank=True, verbose_name='GitHub链接')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    class Meta:
        verbose_name = '用户'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

    def get_absolute_url(self):
        return reverse("users:detail", kwargs={"username": self.username})

    def get_profile_name(self):
        # if self.nickname:
        #     return self.nickname
        return self.username

    def get_full_name(self):  # 解决comments模块的bug
        return self.username

    def get_oneweek_energy(self):
        return list(map(int, self.oneweek_energy.split(";")))  # 返回数字数组

    def set_oneweek_energy(self, oneweek_energy_list):  # 接受数字数组
        new_oneweek_energy = ";".join([str(item) for item in oneweek_energy_list])#数字列表转为字符串
        self.oneweek_energy = new_oneweek_energy

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False


# 注册验证码模型类
class VerifyCode(models.Model):
    email = models.CharField(null=True, blank=True, max_length=45, verbose_name='待验证邮箱')
    code = models.CharField(null=True, blank=True, max_length=45, verbose_name='待使用验证码')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')  # TODO 用于之后设置定时任务，将超过5分钟未使用的、过期的code在数据库删除

    class Meta:
        verbose_name = '注册验证码'
        verbose_name_plural = verbose_name

    def getCode(email):
        try:
            verify_code = VerifyCode.objects.get(email=email).code
        except Exception:
            verify_code = "xxx"
        return verify_code


# 动态模块模型类
class News(models.Model):
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL,
                             related_name='publisher', verbose_name='用户')
    parent = models.ForeignKey("self", blank=True, null=True, on_delete=models.CASCADE,
                               related_name='thread', verbose_name='自关联')
    content = models.TextField(verbose_name='动态内容')
    liked = models.ManyToManyField(User, related_name='liked_news', verbose_name='点赞用户')
    reply = models.BooleanField(default=False, verbose_name='是否为评论')
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    class Meta:
        verbose_name = '动态模块'
        verbose_name_plural = verbose_name
        ordering = ("-created_at",)  # 显示时按数据顺序显示

    def __str__(self):
        return self.content

    def switch_like(self, user):
        """点赞或取消赞"""
        if user in self.liked.all():  # 如果用户已经赞过，则取消赞
            self.liked.remove(user)
            add_today_energy(user, -1)  # 油力值修改
        else:  # 如果用户没有赞过，则添加赞
            self.liked.add(user)
            add_today_energy(user, 1)  # 油力值修改
            # 生成一条通知，通知楼主
            add_one_notification(user, self.user, 'L', self)

    def get_parent(self):
        """返回自关联中的上级记录或者本身"""
        if self.parent:
            return self.parent
        else:
            return self

    def reply_this(self, user, text):
        """
        回复首页的动态
        :param user: 登录的用户
        :param text: 回复的内容
        :return: None
        """
        parent = self.get_parent()
        News.objects.create(
            user=user,
            content=text,
            reply=True,
            parent=parent
        )
        # 生成一条通知，通知楼主
        add_one_notification(user, parent.user, 'R', parent)

    def get_thread(self):
        """关联到当前记录的所有记录"""
        parent = self.get_parent()
        return parent.thread.all()

    def comment_count(self):
        """评论数"""
        return self.get_thread().count()

    def count_likers(self):
        """点赞数"""
        return self.liked.count()

    def get_likers(self):
        """所有点赞用户"""
        return self.liked.all()


# 文章模型类的自定义查询集
class ArticleQuerySet(models.query.QuerySet):
    """自定义QuerySet，提高模型类的可用性"""

    def get_published(self):
        """返回已发表的文章"""
        return self.filter(status="P").select_related('user')

    def get_drafts(self):
        """返回草稿箱的文章"""
        return self.filter(status="D").select_related('user')

    def get_counted_tags(self):
        """统计所有已发布的文章中，每一个标签的数量(大于0的)"""
        tag_dict = {}
        for obj in self.all():
            for tag in obj.tags.names():
                if tag not in tag_dict:
                    tag_dict[tag] = 1

                else:
                    tag_dict[tag] += 1
        return tag_dict.items()


# 定义用户上传的文件路径
def user_article_directory_path(instance, filename):  # 这两参数是django自动赋值的
    ext = filename.split('.')[-1]  # 取出用户上传的文件的文件名的后缀，即：文件类型
    filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)  # 修改文件名避免冲突
    # return the whole path to the file
    return os.path.join("article_pics/%Y/%m/%d/", filename)  # 生成保存路径


# 文章模型类
class Article(models.Model):
    STATUS = (("D", "Draft"), ("P", "Published"))

    title = models.CharField(max_length=255, null=False, unique=True, verbose_name='标题')
    user = models.ForeignKey(User, null=True, related_name="author", on_delete=models.SET_NULL, verbose_name='作者')
    image = models.ImageField(upload_to=user_article_directory_path, verbose_name='文章图片', null=True)
    slug = models.SlugField(max_length=80, null=True, blank=True, verbose_name='(URL)别名')
    status = models.CharField(max_length=1, choices=STATUS, default='D', verbose_name='状态')  # 默认存入草稿箱
    liked = models.ManyToManyField(User, related_name='liked_articles',
                                   verbose_name='点赞用户')  # 根据提示，null对ManyToManyField不起作用
    content = MarkdownxField(verbose_name='内容', null=True)
    edited = models.BooleanField(default=False, verbose_name='是否可编辑')
    tags = TaggableManager(help_text='多个标签使用,(英文)隔开', verbose_name='标签')  # 若为空字符串，则为占位作用
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    objects = ArticleQuerySet.as_manager()

    class Meta:
        verbose_name = '文章'
        verbose_name_plural = verbose_name
        ordering = ("-created_at",)

    def __str__(self):
        return self.title

    def switch_like(self, user):
        """点赞或取消赞"""
        if user in self.liked.all():  # 如果用户已经赞过，则取消赞
            self.liked.remove(user)
            add_today_energy(user, -1)  # 油力值修改
        else:  # 如果用户没有赞过，则添加赞
            self.liked.add(user)
            add_today_energy(user, 1)  # 油力值修改
            # 生成一条通知，通知楼主
            add_one_notification(user, self.user, 'L', self)

    def count_likers(self):
        """点赞数"""
        return self.liked.count()

    def get_likers(self):
        """所有点赞用户"""
        return self.liked.all()

    def save(self, *args, **kwargs):
        if not self.slug:
            # 根据作者和标题生成文章在URL中的别名
            self.slug = slugify(self.title)
        super(Article, self).save(*args, **kwargs)

    def get_markdown(self):
        # 将Markdown文本转换成HTML
        return markdownify(self.content)


# 投票模型类
class Vote(models.Model):
    """使用Django中的ContentType, 同时关联用户对问题和回答的投票"""
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, related_name='qa_vote',
                             on_delete=models.CASCADE, verbose_name='用户')
    value = models.BooleanField(default=True, verbose_name='赞同或反对')  # True赞同，False反对
    # GenericForeignKey设置
    content_type = models.ForeignKey(ContentType, related_name='votes_on', on_delete=models.CASCADE)
    object_id = models.CharField(max_length=255)
    vote = GenericForeignKey('content_type', 'object_id')  # 等同于GenericForeignKey()

    created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    class Meta:
        verbose_name = '投票'
        verbose_name_plural = verbose_name
        unique_together = ('user', 'content_type', 'object_id')  # 联合唯一键
        # SQL优化
        index_together = ('content_type', 'object_id')  # 联合唯一索引


# 问题模型类的自定义查询集
class QuestionQuerySet(models.query.QuerySet):
    """自定义QuerySet，提高模型类的可用性"""

    def get_answered(self):
        """已有答案的问题"""
        return self.filter(has_answer=True).exclude(title='暂无数据，等待占位').select_related('user')

    def get_unanswered(self):
        """未被的回答的问题"""
        return self.filter(has_answer=False).exclude(title='暂无数据，等待占位').select_related('user')

    def get_counted_tags(self):
        """统计所有问题标签的数量(大于0的)"""
        tag_dict = {}
        for obj in self.all():
            for tag in obj.tags.names():
                if tag not in tag_dict:
                    tag_dict[tag] = 1
                else:
                    tag_dict[tag] += 1
        return tag_dict.items()


# 问题模型类
class Question(models.Model):
    STATUS = (("O", "Open"), ("C", "Close"), ("D", "Draft"))

    user = models.ForeignKey(User, related_name="q_author",
                             on_delete=models.CASCADE, verbose_name='提问者', null=True)
    title = models.CharField(max_length=255, unique=True, verbose_name='标题')
    slug = models.SlugField(max_length=80, null=True, blank=True, verbose_name='(URL)别名')
    status = models.CharField(max_length=1, choices=STATUS, default='O',
                              verbose_name='问题状态')
    content = MarkdownxField(verbose_name='内容', null=True, default="")
    tags = TaggableManager(help_text='多个标签使用,(英文)隔开', verbose_name='标签')
    has_answer = models.BooleanField(default=False, verbose_name="接受回答")  # 是否有接受的回答
    votes = GenericRelation(Vote, verbose_name='投票情况', null=True)  # 通过GenericRelation关联到Vote表，不是实际的字段
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    objects = QuestionQuerySet.as_manager()

    class Meta:
        verbose_name = '问题'
        verbose_name_plural = verbose_name
        ordering = ("-created_at",)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super(Question, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    def get_markdown(self):
        return markdownify(self.content)

    def total_votes(self):
        """得票数"""
        dic = Counter(self.votes.values_list('value', flat=True))  # Counter赞同票多少，反对票少数
        return dic[True] - dic[False]

    def get_answers(self):
        """获取所有的回答"""
        return Answer.objects.filter(question=self).select_related('user', 'question')  # self作为参数，当前的问题有多少个回答

    def count_answers(self):
        """回答的数量"""
        return self.get_answers().count()

    def get_upvoters(self):
        """赞同的用户"""
        return [vote.user for vote in self.filter(value=True).select_related('user').prefetch_related('vote')]

    def get_downvoters(self):
        """反对的用户"""
        return [vote.user for vote in self.votes.filter(value=False).select_related('user').prefetch_related('vote')]

    def get_hotscore(self):
        """问题的热度分数"""
        return self.count_answers() + self.total_votes()

    # def __lt__(self, other):
    #     return self.get_hotscore() < other.get_hotscore()


class Answer(models.Model):
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, related_name='a_author', on_delete=models.CASCADE,
                             verbose_name='回答者')
    question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name='问题')
    content = MarkdownxField(verbose_name='内容')
    is_answer = models.BooleanField(default=False, verbose_name='回答是否被接受')
    votes = GenericRelation(Vote, verbose_name='投票情况')
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    class Meta:
        ordering = ('-is_answer', '-created_at')  # 多字段排序
        verbose_name = '回答'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.content

    def get_markdown(self):
        return markdownify(self.content)

    def total_votes(self):
        """得票数"""
        dic = Counter(self.votes.values_list('value', flat=True))  # Counter赞同票多少，反对票少数
        return dic[True] - dic[False]

    def get_upvoters(self):
        """赞同的用户"""
        return [vote.user for vote in self.votes.filter(value=True).select_related('user').prefetch_related('vote')]

    def get_downvoters(self):
        """反对的用户"""
        return [vote.user for vote in self.votes.filter(value=False).select_related('user').prefetch_related('vote')]

    def accept_answer(self):
        """接受回答"""
        # 当一个问题有多个回答的时候，只能采纳一个回答，其它回答一律置为未接受
        answer_set = Answer.objects.filter(question=self.question)  # 查询当前问题的所有回答
        answer_set.update(is_answer=False)  # 一律置为未接受
        # 接受当前回答并保存
        self.is_answer = True
        self.save()
        # 该问题已有被接受的回答，保存
        self.question.has_answer = True
        self.question.save()


class MessageQuerySet(models.query.QuerySet):

    def get_conversation(self, sender, recipient):
        """用户间的私信会话"""
        qs_one = self.filter(sender=sender, recipient=recipient).select_related('sender', 'recipient')  # A发送给B的消息
        qs_two = self.filter(sender=recipient, recipient=sender).select_related('sender', 'recipient')  # B发送给A的消息
        return qs_one.union(qs_two).order_by('created_at')  # 取并集后按时间排序

    def get_most_recent_conversation(self, recipient):
        """获取最近一次私信互动的用户"""
        try:
            qs_sent = self.filter(sender=recipient).select_related('sender', 'recipient')  # 当前登录用户发送的消息
            qs_received = self.filter(recipient=recipient).select_related('sender', 'recipient')  # 当前登录用户接收的消息
            qs = qs_sent.union(qs_received).latest("created_at")  # 最后一条消息
            if qs.sender == recipient:
                # 如果登录用户有发送消息，返回消息的接收者
                return qs.recipient
            # 否则返回消息的发送者
            return qs.sender
        except self.model.DoesNotExist:
            # 如果模型实例不存在，则返回当前用户
            return User.objects.get(username=recipient.username)


class Message(models.Model):
    """用户间私信"""
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    sender = models.ForeignKey(User, related_name='sent_messages',
                               blank=True, null=True, on_delete=models.SET_NULL, verbose_name='发送者')
    recipient = models.ForeignKey(User, related_name='received_messages',
                                  blank=True, null=True, on_delete=models.SET_NULL, verbose_name='接受者')
    message = models.TextField(blank=True, null=True, verbose_name='内容')
    unread = models.BooleanField(default=True, verbose_name='是否未读')

    created_at = models.DateTimeField(db_index=True, auto_now_add=True,
                                      verbose_name='创建时间')  # 没有updated_at，私信发送之后不能修改或撤回

    objects = MessageQuerySet.as_manager()

    class Meta:
        verbose_name = '私信'
        verbose_name_plural = verbose_name
        ordering = ('-created_at',)

    def __str__(self):
        return self.message

    def mark_as_read(self):
        if self.unread:
            self.unread = False
            self.save()


class NotificationQuerySet(models.query.QuerySet):

    def unread(self):
        return self.filter(unread=True).select_related('actor', 'recipient')

    def read(self):
        return self.filter(unread=False).select_related('actor', 'recipient')

    def mark_all_as_read(self, recipient=None):
        """标为已读，可以传入接收者参数"""
        qs = self.unread()
        if recipient:
            qs = qs.filter(recipient=recipient)
        return qs.update(unread=False)

    def mark_all_as_unread(self, recipient=None):
        """标为未读，可以传入接收者参数"""
        qs = self.read()
        if recipient:
            qs = qs.filter(recipient=recipient)
        return qs.update(unread=True)

    def get_most_recent(self, recipient=None):
        """获取最近5条未读通知"""
        qs = self.unread()[:5]
        if recipient:
            qs = qs.filter(recipient=recipient)[:5]
        return qs

    def serialize_latest_notifications(self, recipient=None):
        """序列化最近5条未读通知，可以传入接收者参数"""
        qs = self.get_most_recent(recipient)
        notification_dic = serializers.serialize('json', qs)
        return notification_dic


class Notification(models.Model):
    """参考与模仿自：django-notifications"""
    NOTIFICATION_TYPE = (
        ('L', '赞了'),  # like
        # ('C', '评论了'),  # comment
        ('F', '收藏了'),  # favor
        ('A', '回答了'),  # answer
        ('W', '接受了回答'),  # accept
        ('R', '回复了'),  # reply
        ('I', '登录'),  # logged in
        ('O', '退出'),  # logged out
    )
    uuid_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    actor = models.ForeignKey(User, related_name="notify_actor",
                              on_delete=models.CASCADE, verbose_name="触发者")
    recipient = models.ForeignKey(User, null=True, blank=False,
                                  related_name="notifications", on_delete=models.CASCADE, verbose_name='接收者')
    unread = models.BooleanField(default=True, verbose_name='未读')
    slug = models.SlugField(max_length=80, null=True, blank=True, verbose_name='(URL)别名')
    verb = models.CharField(max_length=1, choices=NOTIFICATION_TYPE, verbose_name="通知类别")
    created_at = models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    # 通用外键
    content_type = models.ForeignKey(ContentType, related_name='notify_action_object', null=True, blank=True,
                                     on_delete=models.CASCADE)
    object_id = models.CharField(max_length=255, null=True, blank=True)
    action_object = GenericForeignKey()  # 或GenericForeignKey("content_type", "object_id")

    objects = NotificationQuerySet.as_manager()

    class Meta:
        verbose_name = "通知"
        verbose_name_plural = verbose_name
        ordering = ("-created_at",)

    def __str__(self):
        if self.action_object:
            return f'{self.actor} {self.get_verb_display()} {self.action_object}'
        return f'{self.actor} {self.get_verb_display()}'

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        if not self.slug:
            self.slug = slugify(f'{self.recipient} {self.uuid_id} {self.verb}')
        super(Notification, self).save()

    def mark_as_read(self):
        if self.unread:
            self.unread = False
            self.save()

    def mark_as_unread(self):
        if not self.unread:
            self.unread = True
            self.save()


# 向数据库增加记录的方法的抽取
def add_one_notification(actor, recipient, verb, action_object):
    """
    :param actor:           request.user对象
    :param recipient:       User Instance 接收者实例，可以是一个或者多个接收者
    :param verb:            str 通知类别
    :param action_object:   Instance 动作对象的实例
    :return:                None
    """
    if actor.username != recipient.username and recipient.username == action_object.user.username:
        # 记录通知内容
        Notification.objects.create(
            actor=actor,
            recipient=recipient,
            verb=verb,
            action_object=action_object
        )


class HotList(models.Model):
    """热榜'缓存'"""
    HOT_TYPE = (
        ('A', '文章热榜'),
        ('Q', '问题热榜'),
    )
    HOT_RANK = ((1, '热榜第一'), (2, '热榜第二'), (3, '热榜第三'), (4, '热榜第四'), (5, '热榜第五'),
                (6, '热榜第六'), (7, '热榜第七'), (8, '热榜第八'), (9, '热榜第九'), (10, '热榜第十'))

    type = models.CharField(max_length=1, choices=HOT_TYPE, verbose_name='统计类别')
    rank = models.SmallIntegerField(choices=HOT_RANK, blank=True, null=True, verbose_name='排名')  # 为null则代表属于词云数据
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    # 通用外键
    content_type = models.ForeignKey(ContentType, related_name='hot_List_object', null=True, blank=True,
                                     on_delete=models.CASCADE)
    object_id = models.CharField(max_length=255, null=True, blank=True)
    hot_object = GenericForeignKey()  # 或GenericForeignKey("content_type", "object_id")

    class Meta:
        verbose_name = '热榜'
        verbose_name_plural = verbose_name
        ordering = ('type', 'rank')

    def get_HotList_articles(self):
        """获取热榜文章列表"""
        artcicles = [artcicle for artcicle in self.objects.filter(type="A").order_by("-rank")]
        return artcicles

    def get_HotList_questions(self):
        questions = [question for question in self.objects.filter(type="Q").order_by("-rank")]
        return questions


class Wordcloud(models.Model):
    wordcloud_data = models.TextField(verbose_name="词云图数据")

    class Meta:
        verbose_name = '词云'
        verbose_name_plural = verbose_name
